// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <iostream>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <string>

#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/exchange/base/symbology/spot_product.h"
#include "coin2/exchange/base/symbology/util.h"
#include "coin2/exchange/binanceus/symbology/currency.h"

namespace coin2::exchange::binance_us::symbology {

using BinanceUsCurrency = coin2::exchange::binance_us::symbology::BinanceUsCurrency;

class BinanceUsProduct : public base::symbology::SpotProduct<BinanceUsCurrency, BinanceUsProduct>,
                         public base::symbology::ProductFromStrMixin<BinanceUsProduct> {
 public:
  typedef base::symbology::SpotProduct<BinanceUsCurrency, BinanceUsProduct> BaseProduct;

 public:
  BinanceUsProduct(const BinanceUsCurrency& base, const BinanceUsCurrency& quote)
      : SpotProduct(
            base,
            quote,
            ToBinanceNativeProduct(base, quote),
            ToBinanceFeedNativeProduct(base, quote),
            base::market::ExchangeType::Binanceus) {}

 public:
  static std::unique_ptr<BinanceUsProduct> FromNativeStrImpl(
      const std::string& native_product,
      int64_t resolve_timestamp) {
    std::unique_ptr<BinanceUsCurrency> base_ptr;
    std::unique_ptr<BinanceUsCurrency> quote_ptr;
    bool success =
        ParseBinanceNativeProduct(native_product, resolve_timestamp, &base_ptr, &quote_ptr);
    if (success) {
      return std::make_unique<BinanceUsProduct>(*base_ptr, *quote_ptr);
    } else {
      return {};
    }
  }
  // Binance Nav feed native symbol use upper case of base currency.
  static std::unique_ptr<BinanceUsProduct> FromNavNativeProduct(
      const std::string& native_product,
      int64_t resolve_timestamp) {
    std::string nav_type = "";
    for (auto _nav_type : {"UP", "DOWN"}) {
      if (boost::algorithm::ends_with(native_product, _nav_type)) {
        nav_type = _nav_type;
        break;
      }
    }
    if (nav_type.empty()) {
      NOTREACHED();
    }
    return std::make_unique<BinanceUsProduct>(
        *BinanceUsCurrency::FromNativeStrNoThrow(native_product, resolve_timestamp),
        *BinanceUsCurrency::FromNativeStrNoThrow("USDT", resolve_timestamp));
  }
  const std::string& nav_native_symbol() const { return base().currency(); }

 private:
  static bool ParseBinanceNativeProduct(
      const std::string& product_str,
      int64_t resolve_timestamp,
      std::unique_ptr<BinanceUsCurrency>* base_ptr,
      std::unique_ptr<BinanceUsCurrency>* quote_ptr) {
    using base::symbology::HasValue;

    const std::string& product_str_upper = StringToUpperCopy(product_str);

    static std::set<std::string> tusd_quote_symbol_set;
    static std::set<std::string> busd_quote_symbol_set = {
        "ADABUSD", "ALGOBUSD", "BNBBUSD",   "BTCBUSD",  "EOSBUSD",  "ETHBUSD", "HBARBUSD",
        "LPTBUSD", "MANABUSD", "MATICBUSD", "NEARBUSD", "OMGBUSD",  "ONEBUSD", "POLYBUSD",
        "REPBUSD", "SHIBBUSD", "SOLBUSD",   "TRXBUSD",  "USDCBUSD", "XTZBUSD", "ZILBUSD"};

    if (tusd_quote_symbol_set.count(product_str_upper)) {
      std::string base = product_str_upper.substr(0, product_str_upper.size() - 4);
      *base_ptr = BinanceUsCurrency::FromNativeStrNoThrow(base, resolve_timestamp);
      *quote_ptr = BinanceUsCurrency::FromNativeStrNoThrow("TUSD", resolve_timestamp);
      return true;
    }
    if (busd_quote_symbol_set.count(product_str_upper)) {
      std::string base = product_str_upper.substr(0, product_str_upper.size() - 4);
      *base_ptr = BinanceUsCurrency::FromNativeStrNoThrow(base, resolve_timestamp);
      *quote_ptr = BinanceUsCurrency::FromNativeStrNoThrow("BUSD", resolve_timestamp);
      return true;
    }

    using std::string_literals::operator""s;
    for (const std::string& quote_cand :
         {"USDT"s, "BTC"s, "USDC"s, "USD"s, "PAX"s, "EUR"s, "GBP"s, "ETH"s, "BNB"s, "TRY"s, "RUB"s, "NGN"s, "DAI"s}) {
      if (StringEndsWith(product_str_upper, quote_cand)) {
        size_t base_len = product_str_upper.size() - quote_cand.size();
        std::string base = product_str_upper.substr(0, base_len);
        *base_ptr = BinanceUsCurrency::FromNativeStrNoThrow(base, resolve_timestamp);
        *quote_ptr = BinanceUsCurrency::FromNativeStrNoThrow(quote_cand, resolve_timestamp);
        return true;
      }
    }
    return false;
  }

  static std::string ToBinanceNativeProduct(
      const BinanceUsCurrency& base,
      const BinanceUsCurrency& quote) {
    using base::market::MarketType;
    std::stringstream ss;
    ss << base.native_currency(MarketType::Spot) << quote.native_currency(MarketType::Spot);
    return ss.str();
  }

  static std::string ToBinanceFeedNativeProduct(
      const BinanceUsCurrency& base,
      const BinanceUsCurrency& quote) {
    return StringToLowerCopy(ToBinanceNativeProduct(base, quote));
  }
};

}  // namespace coin2::exchange::binance_us::symbology
